package cn.iocoder.yudao.module.xiaoBizHandler.service.codegen.inner;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.xiaoBizHandler.convert.codegen.XiaoBizHandlerCodegenConvert;
import cn.iocoder.yudao.module.xiaoBizHandler.dal.dataobject.codegen.XiaoBizHandlerCodegenColumnDO;
import cn.iocoder.yudao.module.xiaoBizHandler.dal.dataobject.codegen.XiaoBizHandlerCodegenTableDO;
import cn.iocoder.yudao.module.xiaoBizHandler.dal.dataobject.codegen.XiaoBizHandlerSchemaColumnDO;
import cn.iocoder.yudao.module.xiaoBizHandler.dal.dataobject.codegen.XiaoBizHandlerSchemaTableDO;
import cn.iocoder.yudao.module.xiaoBizHandler.enums.codegen.XiaoBizHandlerCodegenColumnHtmlTypeEnum;
import cn.iocoder.yudao.module.xiaoBizHandler.enums.codegen.XiaoBizHandlerCodegenColumnListConditionEnum;
import cn.iocoder.yudao.module.xiaoBizHandler.enums.codegen.XiaoBizHandlerCodegenTemplateTypeEnum;
import com.google.common.collect.Sets;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;

import static cn.hutool.core.text.CharSequenceUtil.*;

/**
 * 代码生成器的 Builder，负责：
 * 1. 将数据库的表 {@link XiaoBizHandlerSchemaTableDO} 定义，构建成 {@link XiaoBizHandlerCodegenTableDO}
 * 2. 将数据库的列 {@link XiaoBizHandlerSchemaColumnDO} 构定义，建成 {@link XiaoBizHandlerCodegenColumnDO}
 */
@Component
public class XiaoBizHandlerCodegenBuilder {

    /**
     * 字段名与 {@link XiaoBizHandlerCodegenColumnListConditionEnum} 的默认映射
     * 注意，字段的匹配以后缀的方式
     */
    private static final Map<String, XiaoBizHandlerCodegenColumnListConditionEnum> columnListOperationConditionMappings =
            MapUtil.<String, XiaoBizHandlerCodegenColumnListConditionEnum>builder()
                    .put("name", XiaoBizHandlerCodegenColumnListConditionEnum.LIKE)
                    .put("time", XiaoBizHandlerCodegenColumnListConditionEnum.BETWEEN)
                    .put("date", XiaoBizHandlerCodegenColumnListConditionEnum.BETWEEN)
                    .build();

    /**
     * 字段名与 {@link XiaoBizHandlerCodegenColumnHtmlTypeEnum} 的默认映射
     * 注意，字段的匹配以后缀的方式
     */
    private static final Map<String, XiaoBizHandlerCodegenColumnHtmlTypeEnum> columnHtmlTypeMappings =
            MapUtil.<String, XiaoBizHandlerCodegenColumnHtmlTypeEnum>builder()
                    .put("status", XiaoBizHandlerCodegenColumnHtmlTypeEnum.RADIO)
                    .put("sex", XiaoBizHandlerCodegenColumnHtmlTypeEnum.RADIO)
                    .put("type", XiaoBizHandlerCodegenColumnHtmlTypeEnum.SELECT)
                    .put("image", XiaoBizHandlerCodegenColumnHtmlTypeEnum.UPLOAD_IMAGE)
                    .put("file", XiaoBizHandlerCodegenColumnHtmlTypeEnum.UPLOAD_FILE)
                    .put("content", XiaoBizHandlerCodegenColumnHtmlTypeEnum.EDITOR)
                    .put("time", XiaoBizHandlerCodegenColumnHtmlTypeEnum.DATETIME)
                    .put("date", XiaoBizHandlerCodegenColumnHtmlTypeEnum.DATETIME)
                    .build();

    /**
     * 多租户编号的字段名
     */
    public static final String TENANT_ID_FIELD = "tenant_id";
    /**
     * {@link BaseDO} 的字段
     */
    public static final Set<String> BASE_DO_FIELDS = new HashSet<>();
    /**
     * 新增操作，不需要传递的字段
     */
    private static final Set<String> CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
    /**
     * 修改操作，不需要传递的字段
     */
    private static final Set<String> UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet();
    /**
     * 列表操作的条件，不需要传递的字段
     */
    private static final Set<String> LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
    /**
     * 列表操作的结果，不需要返回的字段
     */
    private static final Set<String> LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet();

    /**
     * Java 类型与 MySQL 类型的映射关系
     */
    private static final Map<String, Set<String>> javaTypeMappings = MapUtil.<String, Set<String>>builder()
            .put(Boolean.class.getSimpleName(), Sets.newHashSet("bit"))
            .put(Integer.class.getSimpleName(), Sets.newHashSet("tinyint", "smallint", "mediumint", "int"))
            .put(Long.class.getSimpleName(), Collections.singleton("bigint"))
            .put(Double.class.getSimpleName(), Sets.newHashSet("float", "double"))
            .put(BigDecimal.class.getSimpleName(), Sets.newHashSet("decimal", "numeric"))
            .put(String.class.getSimpleName(), Sets.newHashSet("tinytext", "text", "mediumtext", "longtext", // 长文本
                    "char", "varchar", "nvarchar", "varchar2")) // 短文本
            .put(Date.class.getSimpleName(), Sets.newHashSet("datetime", "time", "date", "timestamp"))
            .put("byte[]", Sets.newHashSet("blob"))
            .build();

    static {
        Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName()));
        BASE_DO_FIELDS.add(TENANT_ID_FIELD);
        // 处理 OPERATION 相关的字段
        CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
        UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
        LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
        LIST_OPERATION_EXCLUDE_COLUMN.remove("createTime"); // 创建时间，还是可能需要传递的
        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove("createTime"); // 创建时间，还是需要返回的
    }

    public XiaoBizHandlerCodegenTableDO buildTable(XiaoBizHandlerSchemaTableDO schemaTable) {
        XiaoBizHandlerCodegenTableDO table = XiaoBizHandlerCodegenConvert.INSTANCE.convert(schemaTable);
        initTableDefault(table);
        return table;
    }

    /**
     * 初始化 Table 表的默认字段
     *
     * @param table 表定义
     */
    private void initTableDefault(XiaoBizHandlerCodegenTableDO table) {
        // 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 SystemDept
        // 如果不希望 System 前缀，则可以手动在【代码生成 - 修改生成配置 - 基本信息】，将实体类名称改为 Dept 即可
        String moduleName = subBefore(table.getTableName(),
                '_', false);
        // 默认转换，错了可以在前端修改
        if ("x".equals(moduleName.toLowerCase()))   moduleName = "xiaoBizHandler";
        table.setModuleName(moduleName); // 第一个 _ 前缀的前面，作为 module 名字
        table.setBusinessName(toCamelCase(subAfter(table.getTableName(),
                '_', false))); // 第一步，第一个 _ 前缀的后面，作为 module 名字; 第二步，可能存在多个 _ 的情况，转换成驼峰
        table.setClassName(upperFirst(toCamelCase(table.getTableName()))); // 驼峰 + 首字母大写
        table.setClassComment(subBefore(table.getTableComment(), // 去除结尾的表，作为类描述
                '表', true));
        // 这里是默认名，后面还会set一遍
        table.setAuthor("XiaoSiyuan");
        table.setTemplateType(XiaoBizHandlerCodegenTemplateTypeEnum.CRUD.getType());
    }

    public List<XiaoBizHandlerCodegenColumnDO> buildColumns(List<XiaoBizHandlerSchemaColumnDO> schemaColumns) {
        List<XiaoBizHandlerCodegenColumnDO> columns = XiaoBizHandlerCodegenConvert.INSTANCE.convertList(schemaColumns);
        columns.forEach(this::initColumnDefault);
        return columns;
    }

    /**
     * 初始化 Column 列的默认字段
     *
     * @param column 列定义
     */
    private void initColumnDefault(XiaoBizHandlerCodegenColumnDO column) {
        // 处理 Java 相关的字段的默认值
        processColumnJava(column);
        // 处理 CRUD 相关的字段的默认值
        processColumnOperation(column);
        // 处理 UI 相关的字段的默认值
        processColumnUI(column);
    }

    private void processColumnJava(XiaoBizHandlerCodegenColumnDO column) {
        // 处理 javaField 字段
        column.setJavaField(toCamelCase(column.getColumnName()));
        // 处理 dictType 字段，暂无
        // 处理 javaType 字段
        String dbType = StrUtil.subBefore(column.getColumnType(), '(', false);
        javaTypeMappings.entrySet().stream()
                .filter(entry -> entry.getValue().contains(dbType))
                .findFirst().ifPresent(entry -> column.setJavaType(entry.getKey()));
        if (column.getJavaType() == null) {
            throw new IllegalStateException(String.format("column(%s) 的数据库类型(%s) 找不到匹配的 Java 类型",
                    column.getColumnName(), column.getColumnType()));
        }
    }

    private void processColumnOperation(XiaoBizHandlerCodegenColumnDO column) {
        // 处理 createOperation 字段
        column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
                && !column.getPrimaryKey()); // 对于主键，创建时无需传递
        // 处理 updateOperation 字段
        column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
                || column.getPrimaryKey()); // 对于主键，更新时需要传递
        // 处理 listOperation 字段
        column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
                && !column.getPrimaryKey()); // 对于主键，列表过滤不需要传递
        // 处理 listOperationCondition 字段
        columnListOperationConditionMappings.entrySet().stream()
                .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
                .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition()));
        if (column.getListOperationCondition() == null) {
            column.setListOperationCondition(XiaoBizHandlerCodegenColumnListConditionEnum.EQ.getCondition());
        }
        // 处理 listOperationResult 字段
        column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField()));
    }

    private void processColumnUI(XiaoBizHandlerCodegenColumnDO column) {
        // 基于后缀进行匹配
        columnHtmlTypeMappings.entrySet().stream()
                .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
                .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType()));
        // 如果是 Boolean 类型时，设置为 radio 类型.
        // 其它类型，因为字段名可以相对保障，所以不进行处理。例如说 date 对应 datetime 类型.
        if (Boolean.class.getSimpleName().equals(column.getJavaType())) {
            column.setHtmlType(XiaoBizHandlerCodegenColumnHtmlTypeEnum.RADIO.getType());
        }
        // 兜底，设置默认为 input 类型
        if (column.getHtmlType() == null) {
            column.setHtmlType(XiaoBizHandlerCodegenColumnHtmlTypeEnum.INPUT.getType());
        }
    }

}
